using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
using Microsoft.VisualStudio.TestTools.Common;
using Microsoft.VisualStudio.TestTools.Execution;
using Microsoft.VisualStudio.TestTools.TestAdapter;
using System.Security.Permissions;
using System.Text;
using NUnit.Util;
namespace NUnitForVSTS.NUnitTest
{

    /// <summary>
    /// MSTest Adapter that facilitates NUnit Tests.
    /// </summary>
    public class NUnitTestAdapter : MarshalByRefObject, ITestAdapter, IBaseAdapter
    {
        /// <summary>
        /// The current run context
        /// </summary>
        private IRunContext _runContext;
        /// <summary>
        /// The id of the current mstest run
        /// </summary>
        private Guid _runId;

        /// <summary>
        /// Tests which still need to be executed
        /// </summary>
        private Dictionary<string, List<UnitTest>> _testsInFixtures = new Dictionary<string, List<UnitTest>>();
        /// <summary>
        /// Tests which are finished with there result.
        /// </summary>
        private Dictionary<ITestElement, TestResultMessage> _testsFinished = new Dictionary<ITestElement, TestResultMessage>();
        private bool _runStopped;

        /// <summary>
        /// Initializes a new instance of the <see cref="NUnitTestAdapter"/> class.
        /// </summary>
        public NUnitTestAdapter()
        {
           // Add a handler for the UnhandledExceptionEvent  
            AppDomain.CurrentDomain.UnhandledException += LogUnhandledException;
        }

        /// <summary>
        /// Logs the unhandled exception in this domain for debugging perposes
        /// </summary>
        /// <param name="o">sender.</param>
        /// <param name="e">The <see cref="System.UnhandledExceptionEventArgs"/> instance containing the event data.</param>
        private static void LogUnhandledException(object o, UnhandledExceptionEventArgs e)
        {
            string log = @"Adapter domain:" +
                e.ExceptionObject + ":" +
                (e.ExceptionObject as Exception).Message + "\r\n" +
                (e.ExceptionObject as Exception).StackTrace
                ;

            File.WriteAllText(Path.Combine(Path.GetTempPath(), "adapter.log"), log
                );
        }

        /// <summary>
        /// Initializes the specified run context.
        /// </summary>
        /// <param name="context">The context.</param>
        public void Initialize(IRunContext context)
        {
            try
            {
                lock (_testsInFixtures)
                {
                    _runContext = context;
                    _runId = Guid.Empty;
                    if (((_runContext != null) && (_runContext.RunConfig != null)))
                    {
                        if (_runContext.RunConfig.TestRun != null && _runContext.RunConfig.TestRun.Id != null)
                        {
                            _runId = _runContext.RunConfig.TestRun.Id;
                        }

                        if(_runContext.RunConfig.TestElements != null)
                        {
                            foreach (ITestElement element in _runContext.RunConfig.TestElements)
                            {
                                UnitTest nunitTestElement = element as UnitTest;
                                if (nunitTestElement != null)
                                {
                                    string className = nunitTestElement.FullyQualifiedClassName;
                                    List<UnitTest> testClass;

                                    if (!_testsInFixtures.TryGetValue(className, out testClass))
                                    {
                                        testClass = new List<UnitTest>();
                                        _testsInFixtures.Add(className, testClass);
                                    }
                                    testClass.Add(nunitTestElement);
                                }
                            }
                        }
                    }
                    lock (_handlerLock)
                    {
                        _handler = CreateTestExecutionHandler(_runId);
                    }
                    _runStopped = false;
                }
            }
            catch (Exception e)
            {
                Trace.TraceError(e.Message);
                Trace.TraceError(e.StackTrace);
                Console.Error.WriteLine(e.Message);
                Console.Error.WriteLine(e.StackTrace);
            }
        }
        private TestLoaderExecutionHandler _handler;
        private object _handlerLock = new object();

        /// <summary>
        /// Receives the message.
        /// </summary>
        /// <param name="obj">The obj.</param>
        public void ReceiveMessage(object obj)
        { }

        /// <summary>
        /// Pres the test run finished.
        /// </summary>
        /// <param name="runContext">The runContext.</param>
        public void PreTestRunFinished(IRunContext runContext)
        {
        }

        /// <summary>
        /// Resumes the test run.
        /// </summary>
        public void ResumeTestRun()
        {
        }

        /// <summary>
        /// Cleanups this instance.
        /// </summary>
        public void Cleanup()
        {
            Trace.TraceInformation("NUnitTestAdapter.Cleanup was called.");
            try
            {
                lock (_handlerLock)
                {
                    if (_handler != null)
                    {
                        _handler.Cleanup();
                        _handler.Dispose();
                    }
                }
                if (_loaderDomain != null)
                {
                    AppDomain.Unload(_loaderDomain);
                }
            }
            catch (Exception e)
            {
                Trace.TraceError(e.Message);
                Trace.TraceError(e.StackTrace);
                Console.Error.WriteLine(e.Message);
                Console.Error.WriteLine(e.StackTrace);
            }
        }

        /// <summary>
        /// Stops the test run.
        /// </summary>
        public void StopTestRun()
        {
            Trace.TraceInformation("NUnitTestAdapter.StopTestRun was called.");
            _runStopped = true;
            lock (_handlerLock)
            {
                if (_handler != null)
                {
                    try
                    {
                        _handler.CancelRun();
                    }
                    catch (Exception e)
                    {
                        Trace.TraceError(e.Message);
                        Trace.TraceError(e.StackTrace);
                        Console.Error.WriteLine(e.Message);
                        Console.Error.WriteLine(e.StackTrace);
                    }
                }
            }
            lock (_testsInFixtures)
            {
            }
        }

        /// <summary>
        /// Aborts the test run.
        /// </summary>
        public void AbortTestRun()
        {
            Trace.TraceInformation("NUnitTestAdapter.AbortTestRun was called.");
            _runStopped = true;
            lock (_handlerLock)
            {
                if (_handler != null)
                {
                    try
                    {
                        _handler.CancelRun();
                    }
                    catch (Exception e)
                    {
                        Trace.TraceError(e.Message);
                        Trace.TraceError(e.StackTrace);
                        Console.Error.WriteLine(e.Message);
                        Console.Error.WriteLine(e.StackTrace);
                    }
                }
            }
            lock (_testsInFixtures)
            {
            }
        }

        /// <summary>
        /// Pauses the test run.
        /// </summary>
        public void PauseTestRun()
        {
            Trace.TraceInformation("NUnitTestAdapter.PauseTestRun was called.");
        }

        /// <summary>
        /// Runs the specified test element.
        /// </summary>
        /// <param name="testElement">The test element.</param>
        /// <param name="testContext">The test context.</param>
        public void Run(ITestElement testElement, ITestContext testContext)
        {
            Check.NotNull(testElement, "testElement");

            string oldCurrentDirectory = Directory.GetCurrentDirectory();
            string deploymentDir = null;
            if (_runContext != null && _runContext.RunContextVariables != null)
            {
                deploymentDir = _runContext.RunContextVariables.GetStringValue("TestDeploymentDir");
            }
            if (string.IsNullOrEmpty(deploymentDir))
            {
                deploymentDir = Directory.GetCurrentDirectory();
            }
            Directory.SetCurrentDirectory(deploymentDir);
            try
            {
                Trace.TraceInformation(string.Format(CultureInfo.CurrentCulture, "NUnitTestAdapter queing (running) test {0} ID={1}", testElement.Name, testElement.Id));
                UnitTest nunitTest = testElement as UnitTest;
                if (nunitTest == null)
                {
                    throw new ArgumentException(string.Format(CultureInfo.CurrentUICulture, "Wrong Test Type ({0})", testElement));
                }

                string className = nunitTest.FullyQualifiedClassName;

                lock (_testsInFixtures)
                {
                    if (_runStopped)
                    {
                        return;
                    }
                    List<UnitTest> testClass;
                    if (_testsInFixtures.TryGetValue(className, out testClass))
                    {
                        _testsInFixtures.Remove(className);

                        IDictionary<ITestElement, UnitTestResult> result = null;
                        try
                        {
                            TestLoaderExecutionHandler handler;
                            lock (_handlerLock)
                            {
                                handler = _handler;
                            }
                            result = handler.ExecuteTestForClass(testClass);
                            foreach (var item in result)
                            {
                                _testsFinished.Add(item.Key, item.Value);
                            }
                        }
                        catch (ThreadAbortException e)
                        {
                            Thread.ResetAbort();
                            foreach (var item in testClass)
                            {
                                UnitTestResult error = ResultFactory.CreateErrorResult(nunitTest, e, _runId);

                                _testsFinished.Add(item, error);
                            }
                        }
                        catch (Exception e)
                        {
                            foreach (var item in testClass)
                            {
                                UnitTestResult error = ResultFactory.CreateErrorResult(nunitTest, e, _runId);

                                _testsFinished.Add(item, error);
                            }
                        }
                    }
                    TestResultMessage testMessage;
                    if (!_testsFinished.TryGetValue(nunitTest, out testMessage))
                    {
                        UnitTestResult error = ResultFactory.CreateErrorResult(nunitTest, "Test Adapter lost test during execution. Reason for this could be that there is a testclass with the same name and namespace in different assemblies.", _runId);
                        testMessage = error;
                    }
                    else
                    {
                        _testsFinished.Remove(nunitTest);
                    }
                    testContext.ResultSink.AddResult(testMessage);
                }
                Directory.SetCurrentDirectory(oldCurrentDirectory);
            }
            catch (Exception e)
            {
                Trace.TraceError(e.Message);
                Trace.TraceError(e.StackTrace);
                Console.Error.WriteLine(e.Message);
                Console.Error.WriteLine(e.StackTrace);
                if(testContext != null && testContext.ResultSink != null)
                {
                    TestResultMessage result = new TestResultMessage(Environment.MachineName, _runId, TestMessageKind.TextMessage);
                    result.SystemException = e;
                    testContext.ResultSink.AddResult(result);
                }
            }
        }

        /// <summary>
        /// Obtains a lifetime service object to control the lifetime policy for this instance.
        /// </summary>
        /// <returns>
        /// An object of type <see cref="T:System.Runtime.Remoting.Lifetime.ILease"/> used to control the lifetime policy for this instance. This is the current lifetime service object for this instance if one exists; otherwise, a new lifetime service object initialized to the value of the <see cref="P:System.Runtime.Remoting.Lifetime.LifetimeServices.LeaseManagerPollTime"/> property.
        /// </returns>
        /// <exception cref="T:System.Security.SecurityException">
        /// The immediate caller does not have infrastructure permission.
        /// </exception>
        /// <PermissionSet>
        /// 	<IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="RemotingConfiguration, Infrastructure"/>
        /// </PermissionSet>
        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
        public override object InitializeLifetimeService()
        {
            return null;
        }

        /// <summary>
        /// Gets the test execution handler.
        /// </summary>
        /// <returns></returns>
        private TestLoaderExecutionHandler CreateTestExecutionHandler(Guid runId)
        {
            AppDomainSetup domainSetup = new AppDomainSetup();
            Type loaderType = typeof(TestLoaderExecutionHandler);
            domainSetup.ApplicationBase = Path.GetDirectoryName(loaderType.Assembly.Location);
            _loaderDomain = AppDomain.CreateDomain("NUnit Test ExecutionDomain:" + Guid.NewGuid().ToString(), loaderType.Assembly.Evidence, domainSetup);
            return _loaderDomain.CreateInstanceAndUnwrap(loaderType.Assembly.FullName, loaderType.FullName, true, BindingFlags.CreateInstance, null, new object[]{runId}, CultureInfo.CurrentCulture, new object[]{}, GetType().Assembly.Evidence) as TestLoaderExecutionHandler;
        }
        private AppDomain _loaderDomain;
    }
}